


#include "bulletinboard.h"

#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>

#include <cctype>
#include <climits>


using namespace std;


namespace
{
    // A convenience method to convert strings to integers.
    int intFromString( const string & s )
    {
        istringstream iss( s );
        int i;
        if ( !(iss >> i) )
            return INT_MIN;
        return i;
    }
}


BBMessage::BBMessage( int id, const string & user, const string & message )
  : m_id( id ),
    m_user( user ),
    m_text( message )
{
}


BBMessage::BBMessage()
  : m_id( 0 ),
    m_user(),
    m_text()
{
}


int BBMessage::id() const
{
    return m_id;
}


string BBMessage::user() const
{
    return m_user;
}


string BBMessage::text() const
{
    return m_text;
}





BulletinBoard::BulletinBoard()
  : m_fileName( "bb.log" ),
    m_lastUsedId( 0 ),
    m_loaded( false )
{
    pthread_rwlock_init( &m_rwlock, 0 );
}


BulletinBoard::~BulletinBoard()
{
    pthread_rwlock_destroy( &m_rwlock );
}


void BulletinBoard::setFileName( const string & file )
{
    pthread_rwlock_wrlock( &m_rwlock );
    m_fileName = file;
    pthread_rwlock_unlock( &m_rwlock );
}


string BulletinBoard::interpretCommand( const string & command, string & user, bool & done )
{
    const char ws[] = " \t\r\n";
    int start = command.find_first_not_of( ws );

    // Ignore empty lines.
    if ( start == string::npos )
        return string();

    ostringstream reply;

    int endOfword = command.find_first_of( ws, start );
    string firstWord = command.substr( start, endOfword - start );
    transform( firstWord.begin(), firstWord.end(), firstWord.begin(), (int(*)(int))toupper );

    string rest;
    int startOfRest = command.find_first_not_of( ws, endOfword );
    if ( startOfRest != string::npos )
        rest = command.substr( startOfRest );

    if ( firstWord == "QUIT" )
    {
        done = true;
        reply << "BYE Come back soon." << endl;
    }
    else if ( rest.empty() )
    {
        reply << "ERROR SYNTAX No arguments given to " << firstWord << '.' << endl;
    }
    if ( firstWord == "USER" )
    {
        user = rest;
        reply << "HELLO " << rest << " Welcome to the BBServ board." << endl;
    }
    else if ( firstWord == "READ" )
    {
        int id = intFromString( rest );
        BBMessage m;
        int result = read( id, m );
        if ( result == IdNotFound )
            reply << "UNKNOWN " << id << " No message was found on the board with that ID." << endl;
        else if ( result == ErrorOpeningForReading )
            reply << "ERROR READ Could not open the database file for reading." << endl;
        else if ( result == ErrorWhileReading )
            reply << "ERROR READ Error encountered while reading from database file." << endl;
        else
            reply << "MESSAGE " << m.id() << ' ' << m.user() << '/' << m.text() << endl;
    }
    else if ( firstWord == "WRITE" )
    {
        int id = write( user, rest );
        if ( id == IdNotFound )
            reply << "UNKNOWN " << id << " No message was found on the board with that ID." << endl;
        else if ( id == ErrorOpeningForReading )
            reply << "ERROR READ Could not open the database file for reading." << endl;
        else if ( id == ErrorOpeningForWriting )
            reply << "ERROR READ Could not open the database file for writing." << endl;
        else if ( id == ErrorWhileWriting )
            reply << "ERROR READ Error encountered while writing to database file." << endl;
        else
            reply << "WROTE " << id << endl;
    }
    else if ( firstWord == "REPLACE" )
    {
        int slashPos = rest.find('/');
        if ( slashPos == string::npos )
        {
            reply << "ERROR SYNTAX The message ID and message text were not separated by a slash." << endl;
        }
        else
        {
            int id = intFromString( rest.substr( 0, slashPos ) );
            int result = replace( id, user, rest.substr( slashPos + 1 ) );
            if ( result == IdNotFound )
                reply << "UNKNOWN " << id << " No message was found on the board with that ID." << endl;
            else if ( result == ErrorOpeningForReading )
                reply << "ERROR READ Could not open the database file for reading." << endl;
            else if ( result == ErrorWhileReading )
                reply << "ERROR READ Error encountered while reading from database file." << endl;
            else if ( result == ErrorOpeningForWriting )
                reply << "ERROR READ Could not open the database file for writing." << endl;
            else if ( result == ErrorWhileWriting )
                reply << "ERROR READ Error encountered while writing to database file." << endl;
            else
                reply << "WROTE " << id << endl;
        }
    }
    else
    {
        reply << "ERROR SYNTAX Unrecognized command " << firstWord << '.' << endl;
    }

    return reply.str();
}


int BulletinBoard::load()
{
    int result = NoError;
    
    pthread_rwlock_rdlock( &m_rwlock );
    bool loaded = m_loaded;
    pthread_rwlock_unlock( &m_rwlock );
    if ( loaded )
        return result;

    pthread_rwlock_wrlock( &m_rwlock );

    ifstream ifs( m_fileName.c_str() );
    if ( ifs.fail() )
    {
        result = ErrorOpeningForReading;
    }
    else
    {
        m_lastUsedId = 0;
        m_messages.clear();

        string line;
        while ( getline( ifs, line ) )
        {
            if ( ifs.fail() )
            {
                result = ErrorWhileReading;
                break;
            }

            printf( "%s\n", line.c_str() );

            int firstSlash = line.find( '/' );
            int secondSlash = line.find( '/', firstSlash + 1 );
            if ( firstSlash == string::npos || secondSlash == string::npos )
                cerr << "Invalid message line:" << line << endl;

            istringstream iss( line.substr( 0, firstSlash ) );
            int id;
            iss >> id;

            if ( id > m_lastUsedId )
                m_lastUsedId = id;

            string user = line.substr( firstSlash + 1, secondSlash - firstSlash - 1 );
            string message = line.substr( secondSlash + 1 );

            m_messages[id] = BBMessage( id, user, message );
        }
    }

    m_loaded = result == NoError;

    pthread_rwlock_unlock( &m_rwlock );

    return result;
}


int BulletinBoard::read( int messageId, BBMessage & message )
{
    int result = load();
    if ( result != NoError )
        return result;

    pthread_rwlock_rdlock( &m_rwlock );

    map<int,BBMessage>::const_iterator it = m_messages.find( messageId );
    if ( it != m_messages.end() )
        message = (*it).second;
    else
        result = IdNotFound;

    pthread_rwlock_unlock( &m_rwlock );

    return result;
}


int BulletinBoard::write( const string & user, const string & text )
{
    int result = load();
    if ( result != NoError )
        return result;

    pthread_rwlock_wrlock( &m_rwlock );

    BBMessage m( m_lastUsedId + 1, user, text );

    ofstream ofs( m_fileName.c_str(), ios_base::out | ios_base::app );
    if ( ofs.fail() )
    {
        result = ErrorOpeningForWriting;
        m_loaded = false;
    }
    else
    {
        ofs << m.id() << '/' << m.user() << '/' << m.text() << endl;
        if ( ofs.fail() )
        {
            result = ErrorWhileWriting;
            m_loaded = false;
        }
        else
        {
            m_messages[m.id()] = m;
            ++m_lastUsedId;
            result = m.id();
        }
    }

    pthread_rwlock_unlock( &m_rwlock );

    return result;
}


int BulletinBoard::replace( int messageId, const string & user, const string & text )
{
    int result = load();
    if ( result != NoError )
        return result;

    pthread_rwlock_wrlock( &m_rwlock );

    if ( m_messages.find( messageId ) == m_messages.end() )
    {
        result = IdNotFound;
    }
    else
    {
        BBMessage m( messageId, user, text );

        ofstream ofs( m_fileName.c_str(), ios_base::out | ios_base::app );
        if ( ofs.fail() )
        {
            result = ErrorOpeningForWriting;
            m_loaded = false;
        }
        else
        {
            ofs << m.id() << '/' << m.user() << '/' << m.text() << endl;
            if ( ofs.fail() )
            {
                result = ErrorWhileWriting;
                m_loaded = false;
            }
            else
            {
                m_messages[m.id()] = m;
                result = m.id();
            }
        }
    }

    pthread_rwlock_unlock( &m_rwlock );

    return result;
}


